#coding=utf-8
from __future__ import with_statement
#   Author:Zhangbo
#   Email:zhangboks@gmail.com
#   QQ(If you have):513364476

import os
import select
import time
import bisect
import fcntl
import logging
import errno

try:
    import signal
except ImportError:
    signal = None

if not hasattr(select, "epoll"):
    raise


class Reactor(object):
    """
        Reactor class use epoll only
    """
    _EPOLLIN   = 0x0001
    _EPOLLLPRI = 0X0002
    _EPOLLOUT  = 0X0004
    _EPOLLERR  = 0X0008
    _EPOLLHUP  = 0X0010
    _EPOORDHUP = 0X2000
    _EPOLLLET  = (1 << 31)
    _EPOLLONESHOT = (1 << 30)

    READEV = _EPOLLIN
    WRITEEV = _EPOLLOUT
    ERROREV = _EPOLLERR | _EPOLLHUP | _EPOORDHUP

    def __init__(self):
        self._poll = select.epoll()
        self._close_exec(self._poll.fileno())

        self._ev_handlers = dict()
        self._events = dict()
        self._ev_callbacks = list()
        self._ev_timeout = list()
        self._re_stop = False
        self._re_run = False
        self._block_time = None

    @classmethod
    def instance(cls):
        if not hasattr(cls, "_instance"):
            cls._instance = cls()
        return cls._instance

    @classmethod
    def initizlized(cls):
        return hasattr(cls, "_instance")

    def _close_exec(self, fd):
        flags = fcntl.fcntl(fd, fcntl.F_GETFD)
        fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC)

    def _set_nonblocking(self, fd):
        flags = fcntl.fcntl(fd, fcntl.F_GETFL)
        fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)

    def _set_waker_self(self):
        r, w = os.pipe()

        self._set_nonblocking(r)
        self._close_exec(r)
        self._set_nonblocking(w)
        self._close_exec(w)

        self._waker_reader = os.fdopen(r, "rb", 0)
        self._waker_writer = os.fdopen(w, "wb", 0)

        self.add_handler(r, self._read_waker, self.READEV)

    def _read_waker(self, fd, events):
        try:
            while 1:
                self._waker_reader.read()
        except IOError:
            pass

    def add_handler(self, fd, handler, events):
        try:
            self._ev_handlers[fd] = handler
            self._poll.register(fd, events | self.ERROREV)
        except (OSError, IOError):
            logging.error("IOError, fd was in epoll")

    def mod_handler(self, fd, events):
        try:
            self._poll.modify(fd, events | self.ERROREV)
        except (OSError, IOError):
            logging.error("Error, fd not in epoll")

    def mov_handler(self, fd):
        try:
            self._ev_handlers.pop(fd, None)
            self._events.pop(fd, None)
            self._poll.unregister(fd)
        except Exception:
            logging.debug("Error in mov_handler", exc_info=True)

    def _run_callback(self, callback):
        try:
            callback()
        except (KeyboardInterrupt, SystemExit):
            raise
        except:
            self._handle_callback_exception(callback)

    def _handle_callback_exception(self, callback):
        logging.error("Exception when call %r", callback, exc_info=True)

    def set_epoll_blocking_time(self, callback=signal.SIG_DFL, seconds=None):
        if not seconds:
            return
        self._block_time = seconds
        signal.signal(signal.SIGALRM, callback)

    def add_timeout_ev(self, deadline, callback):
        _timeout = TimeoutEv(deadline, callback)
        bisect.insort(self._timeouts, _timeout)
        return _timeout

    def mov_timeout_ev(self, timeout):
        self._timeouts.remove(timeout)

    def _wake_self(self):
        try:
            self._waker_writer.write("0")
        except:
            pass

    def run(self):
        if self._re_stop:
            self._re_stop = False
        self._re_run = True

        while 1:
            _epoll_timeout = 0.2
            for c in self._ev_callbacks:
                self._run_callback(c)

            self._ev_callbacks = []

            if self._ev_timeout:
                now = time.time()
                while self._ev_timeout and self._ev_timeout[0].deadline <= now:
                    timeout_ev = self._ev_timeout.pop(0)
                    self._run_callback(timeout_ev.callback)
                if self._ev_timeout:
                    _epoll_timeout = min(
                        self._ev_timeout[0].deadline - now, _epoll_timeout)

            if not self._re_run:
                break

            # remove timer
            if self._block_time:
                signal.setitimer(signal.ITIMER_REAL, 0, 0)

            try:
                _events_ret = self._poll.poll(_epoll_timeout)
            except Exception, e:
                if (getattr(e, 'errno', None) == errno.EINTR) or \
                   (hasattr(e, 'args') and len(e.args) > 1 and e.args[0] == errno.EINTR):
                    continue
                else:
                    raise
            #recover timer
            if self._block_time:
                signal.setitimer(signal.ITIMER_REAL, self._block_time, 0)

            self._events.update(_events_ret)
            while self._events:
                fd, events = self._events.popitem()
                try:
                    self._ev_handlers[fd](fd, events)
                except (KeyboardInterrupt, SystemExit):
                    raise
                except (OSError, IOError), e:
                    if e.args[0] == errno.EPIPE:
                        # client close connection
                        pass
                    else:
                        logging.error("Exception in epoll returned callbacks for fd %d:", fd, exc_info=True)
                except:
                    logging.error("Exception in epoll returned callbacks for fd %d:", fd, exc_info=True)

        self._re_stop = False
        if self._block_time:
            signal.setitimer(signal.ITIMER_REAL, 0, 0)

    def stop(self):
        self._re_stop = True
        self._re_run = False
        for fd in self._ev_handlers.keys():
            try:
                fd.close()
            except OSError, IOError:
                continue
        for ev_time in self._ev_timeout:
            self.mov_timeout_ev(ev_time)
        self._wake_self()
        self._poll.close()


class TimeoutEv(object):

    __slots__ = ['deadline', 'callback']

    def __init__(self, deadline, callback):
        self.deadline = deadline
        self.callback = callback

    #for bisect.insort's compare function
    def __lt__(self, other):
        return cmp(self.deadline, other.deadline)


if __name__ == "__main__":
    import socket
    import functools

    def connection_ready(sock, fd, events):
        while 1:
            try:
                con, add = sock.accept()
            except socket.error, e:
                if not e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): 
                    raise
                return
            con.setblocking(0)
            print con, add

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setblocking(0)
    sock.bind(("", 1115))
    sock.listen(512)

    reactor = Reactor.instance()
    reactor.add_handler(sock.fileno(
    ), functools.partial(connection_ready, sock), reactor.READEV)
    reactor.run()
